#include "stdafx.h"
#include "VideoDecode.h"
#include <process.h>


VideoDecode::VideoDecode()
{
}


VideoDecode::~VideoDecode()
{
    DecodeStop();
    m_showFrame = NULL;
    while (!m_cacheFrame.empty())
    {
        AVFrame* pFrame = m_cacheFrame.front();
        m_cacheFrame.pop();
        av_free((unsigned char*)pFrame->data[0]);
        av_frame_free(&pFrame);
    }
    while (!m_poolFrame.empty())
    {
        AVFrame* pFrame = m_poolFrame.front();
        m_poolFrame.pop();
        av_free((unsigned char*)pFrame->data[0]);
        av_frame_free(&pFrame);
    }
    if(m_pImgConvertCtx)
        sws_freeContext(m_pImgConvertCtx);
    if (m_pCodecCtxVideo)
        avcodec_close(m_pCodecCtxVideo);
    if (m_pFormatCtx)
    {
        avformat_close_input(&m_pFormatCtx);
        avformat_free_context(m_pFormatCtx);
    }
}


unsigned int __stdcall VideoDecode::WorkingThread(void *lp)
{
    VideoDecode* pThis = static_cast<VideoDecode*>(lp);
    int ret =  pThis->DecodeThread();
    
    return ret;
}

AVFrame* VideoDecode::GetShowFrame() 
{
    if (m_showFrame)
        return m_showFrame;
    if (m_cacheFrame.empty())
        return NULL;
    m_showFrame = m_cacheFrame.front();

    int64_t pts = av_frame_get_best_effort_timestamp(m_showFrame);
    if (pts == AV_NOPTS_VALUE)
        pts = 0;
    m_showtime = pts * GetTimeBase();

    return m_showFrame;
}
int VideoDecode::NextFrame()
{
    if (m_cacheFrame.empty())
        return 0;
    AVFrame* pFrame = m_cacheFrame.front();
    m_poolFrame.push(pFrame);
    m_cacheFrame.pop();
    m_showFrame = NULL;
    if (m_cacheFrame.empty())
        return 0;
    return 1;
}

int VideoDecode::InitDecode(const std::string& fileName, AVPixelFormat pixFormat, int maxCache)
{
    if (DECODE != m_decode)
        return m_decode;

    m_filepath = fileName;
    m_pixFormat = pixFormat;

    av_register_all();
    avformat_network_init();
    m_pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&m_pFormatCtx, m_filepath.c_str(), NULL, NULL) != 0) {
        printf("Couldn't open input stream.\n");
        return -1;
    }
    if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0) {
        printf("Couldn't find stream information.\n");
        return -1;
    }
    videoindex = -1;
    for (int i = 0; i < m_pFormatCtx->nb_streams; i++) {
        if (m_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoindex = i;
            break;
        }
    }
    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
        return -1;
    }
    m_pCodecCtxVideo = m_pFormatCtx->streams[videoindex]->codec;
    AVCodec	*pCodecVideo = avcodec_find_decoder(m_pCodecCtxVideo->codec_id);
    if (pCodecVideo == NULL) {
        printf("Codec not found.\n");
        return -1;
    }
    if (avcodec_open2(m_pCodecCtxVideo, pCodecVideo, NULL) < 0) {
        printf("Could not open codec.\n");
        return -1;
    }
    
    //Output Info-----------------------------
    printf("--------------- File Information ----------------\n");
    av_dump_format(m_pFormatCtx, 0, m_filepath.c_str(), 0);
    printf("-------------------------------------------------\n");
    m_pImgConvertCtx = sws_getContext(m_pCodecCtxVideo->width, m_pCodecCtxVideo->height, m_pCodecCtxVideo->pix_fmt,
        m_pCodecCtxVideo->width, m_pCodecCtxVideo->height, m_pixFormat, SWS_BICUBIC, NULL, NULL, NULL);
    m_width = m_pCodecCtxVideo->width;
    m_height = m_pCodecCtxVideo->height;

    bool one = true;
    m_timeBase = av_q2d(m_pFormatCtx->streams[videoindex]->time_base);

    m_pFormatCtx->duration != AV_NOPTS_VALUE;
    int64_t duration = m_pFormatCtx->duration + 5000;
    m_countTime = duration / AV_TIME_BASE;

    m_decode = DECODE_INIT;

    DecodeStart();

    return 0;
}


int VideoDecode::Seek(int sec)
{
    int ret = -1;
    int64_t time = (int64_t)(sec / m_timeBase);

    ret = av_seek_frame(m_pFormatCtx, videoindex, time, AVSEEK_FLAG_BACKWARD);

    m_seek = true;
    m_showtime = 0;
    return ret;
}

int VideoDecode::DecodeStart()
{
    if (m_thread)
        return 0;
    m_run = true;
    m_thread = (HANDLE)_beginthreadex(NULL, 0, VideoDecode::WorkingThread, this, 0, NULL);
    return 0;
}

int VideoDecode::DecodeStop()
{
    m_run = false;
    if (m_thread)
    {
        WaitForSingleObjectEx(m_thread, INFINITE, 1);
        m_thread = NULL;
    }
    return 0;
}


int VideoDecode::DecodeThread()
{
    m_decode = DECODE_RUNING;
    AVFrame	* pFrame = av_frame_alloc();

    AVPacket *packet{ NULL };
    packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    int ret = -1, got_picture = -1;
    while (m_run && av_read_frame(m_pFormatCtx, packet) >= 0) {
        if (m_seek)
        {
            while (!m_cacheFrame.empty())
            {
                AVFrame* pFrame = m_cacheFrame.front();
                m_poolFrame.push(pFrame);
                m_cacheFrame.pop();
            }
            m_seek = false;
            continue;
        }

        if (packet->stream_index == videoindex) {
            got_picture = -1;
            ret = avcodec_decode_video2(m_pCodecCtxVideo, pFrame, &got_picture, packet);
            if (ret < 0) {
                printf("Decode Error.\n");
                return -1;
            }
            if (got_picture) {   
                while (m_run && m_cacheFrame.size() > m_maxCache)
                {
                    Sleep(10);
                    if (m_seek)
                        break;
                }
                AVFrame	*pFrameYUV = NULL;
                if (m_poolFrame.empty())
                {
                    pFrameYUV = av_frame_alloc();
                    unsigned char *out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(m_pixFormat, m_pCodecCtxVideo->width, m_pCodecCtxVideo->height, 1));
                    av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
                        m_pixFormat, m_pCodecCtxVideo->width, m_pCodecCtxVideo->height, 1);
                }
                else
                {
                    pFrameYUV = m_poolFrame.front();
                    m_poolFrame.pop();
                }

                int64_t pts = av_frame_get_best_effort_timestamp(pFrame);

                sws_scale(m_pImgConvertCtx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, m_pCodecCtxVideo->height,
                    pFrameYUV->data, pFrameYUV->linesize);
                //av_frame_free(&pFrameYUV);
                //av_free(out_buffer);
                pFrameYUV->best_effort_timestamp = pFrame->best_effort_timestamp;
                m_cacheFrame.push(pFrameYUV);
            }
            else
            {
                ;
            }
        }
        av_free_packet(packet);
    }
    //flush decoder
    //FIX: Flush Frames remained in Codec
    while (m_run) {
        ret = avcodec_decode_video2(m_pCodecCtxVideo, pFrame, &got_picture, packet);
        if (ret < 0)
            break;
        if (!got_picture)
            break;
        AVFrame	*pFrameYUV = av_frame_alloc();
        unsigned char *out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(m_pixFormat, m_width, m_height, 1));
        av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
            m_pixFormat, m_width, m_height, 1);
        int64_t pts = av_frame_get_best_effort_timestamp(pFrame);

        sws_scale(m_pImgConvertCtx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, m_pCodecCtxVideo->height,
            pFrameYUV->data, pFrameYUV->linesize);
        pFrameYUV->best_effort_timestamp = pFrame->best_effort_timestamp;
        m_cacheFrame.push(pFrameYUV);
    }
    sws_freeContext(m_pImgConvertCtx);
    m_pImgConvertCtx = NULL;

    av_free_packet(packet);
    av_free(packet);

    av_frame_free(&pFrame);
    
    avcodec_close(m_pCodecCtxVideo);
    m_pCodecCtxVideo = NULL;

    avformat_close_input(&m_pFormatCtx);
    avformat_free_context(m_pFormatCtx);
    m_pFormatCtx = NULL;

    m_decode = DECODE_END;
    m_run = false;
    return 0;
}